﻿namespace NetOwls.Tutorials.Mqtt.Common
{
    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using MQTTnet;
    using MQTTnet.Client;

    /// <summary>
    ///     提供了管理客户端与 MQTT 服务连接的基本方法。
    /// </summary>
    /// <seealso>
    ///     <cref>IMqttConnection</cref>
    /// </seealso>
    /// <seealso>
    ///     <cref>System.Threading.Tasks.Task</cref>
    /// </seealso>
    /// <seealso>
    ///     <cref>MQTTnet.Client.IMqttClient</cref>
    /// </seealso>
    /// <seealso>
    ///     <cref>IMqttConnectionCredentials</cref>
    /// </seealso>
    /// <seealso>
    ///     <cref>MQTTnet.Client.IMqttClientOptions</cref>
    /// </seealso>
    public abstract class MqttConnectionBase : IMqttConnection
    {
        #region Protected Methods

        /// <summary>
        ///     创建一个 MQTT 客户端连接配置实例。
        /// </summary>
        /// <returns>
        ///     实现了
        ///     <see>
        ///         <cref>IMqttClientOptions</cref>
        ///     </see>
        ///     接口的对象实例。
        /// </returns>
        /// <seealso>
        ///     <cref>IMqttClientOptions</cref>
        /// </seealso>
        protected virtual IMqttClientOptions CreateClientOptions()
        {
            var options = new MqttClientOptionsBuilder()
                .WithCleanSession()
                .WithClientId($"mqtt.client.{Guid.NewGuid().ToString().ToLower().Replace("-", string.Empty)}")
                .WithCommunicationTimeout(new TimeSpan(0, 0, 5))
                .WithTcpServer(ServerIPAddress, ServerPortNumber);
            if (Credentials != null && Credentials.GetType() != typeof(NoneAuthenticationCredentials) &&
                !string.IsNullOrEmpty(Credentials.UserName))
                options.WithCredentials(Credentials.UserName, Credentials.Password);
            return options.Build();
        }

        /// <summary>
        ///     初始化客户端与 MQTT 服务的连接对象。
        /// </summary>
        protected virtual void InitializeConnection()
        {
            if (Client == null)
            {
                Trace.TraceInformation($"尝试初始化 MQTT 连接对象实例。");
                Client = new MqttFactory().CreateMqttClient();
            }
        }

        /// <summary>
        ///     尝试连接到 MQTT 服务。
        /// </summary>
        /// <returns>
        ///     <see>
        ///         <cref>Task</cref>
        ///     </see>
        ///     类型的对象实例。
        /// </returns>
        /// <seealso>
        ///     <cref>Task</cref>
        /// </seealso>
        protected virtual async Task InternalConnectAsync()
        {
            try
            {
                InitializeConnection();
                if (!Client.IsConnected)
                {
                    var options = CreateClientOptions();
                    Trace.TraceWarning("尝试连接到 MQTT 服务。");
                    await Client.ConnectAsync(options);
                    Trace.TraceWarning("MQTT 服务已经连接。");
                }
            }
            catch (Exception error)
            {
                Trace.TraceError($"尝试连接到 MQTT 服务时，引发了一个 {error.GetType().FullName} 类型的异常：{error.Message}");
            }
        }

        /// <summary>
        ///     尝试断开与 MQTT 服务的连接。
        /// </summary>
        /// <returns>
        ///     <see>
        ///         <cref>Task</cref>
        ///     </see>
        ///     类型的对象实例。
        /// </returns>
        /// <seealso>
        ///     <cref>Task</cref>
        /// </seealso>
        protected virtual async Task InternalDisconnectAsync()
        {
            try
            {
                if (Client != null && Client.IsConnected)
                {
                    Trace.TraceWarning("尝试断开与 MQTT 服务的连接。");
                    await Client.DisconnectAsync();
                    Trace.TraceWarning("MQTT 服务的连接已经断开。");
                }
            }
            catch (Exception error)
            {
                Trace.TraceError($"尝试断开与 MQTT 服务的连接时，引发了一个 {error.GetType().FullName} 类型的异常：{error.Message}");
            }
        }

        #endregion

        #region Properties

        #region IMqttConnection Implements

        /// <summary>
        ///     获取客户端与 MQTT 服务的连接对象。
        ///     <para>
        ///         实现了
        ///         <see>
        ///             <cref>MQTTnet.Client.IMqttClient</cref>
        ///         </see>
        ///         接口的对象实例。
        ///     </para>
        /// </summary>
        /// <seealso>
        ///     <cref>MQTTnet.Client.IMqttClient</cref>
        /// </seealso>
        public IMqttClient Client { get; protected set; }

        /// <summary>
        ///     获取 MQTT 服务连接所需的凭据信息。
        ///     <para>
        ///         实现了
        ///         <see>
        ///             <cref>IMqttConnectionCredentials</cref>
        ///         </see>
        ///         接口的对象实例。
        ///     </para>
        /// </summary>
        /// <seealso>
        ///     <cref>IMqttConnectionCredentials</cref>
        /// </seealso>
        public abstract IMqttConnectionCredentials Credentials { get; }

        /// <summary>
        ///     获取 MQTT 服务 IP 地址或名称。
        /// </summary>
        public abstract string ServerIPAddress { get; }

        /// <summary>
        ///     获取 MQTT 服务端口号。
        /// </summary>
        public abstract int ServerPortNumber { get; }

        #endregion

        #endregion

        #region Methods

        #region IMqttConnection Implements

        /// <summary>
        ///     尝试异步连接 MQTT 服务。
        /// </summary>
        /// <returns>
        ///     <see>
        ///         <cref>System.Threading.Tasks.Task</cref>
        ///     </see>
        ///     类型的对象实例。
        /// </returns>
        /// <seealso>
        ///     <cref>System.Threading.Tasks.Task</cref>
        /// </seealso>
        public async Task ConnectAsync()
        {
            await InternalConnectAsync();
        }

        /// <summary>
        ///     尝试异步断开 MQTT 服务的连接。
        /// </summary>
        /// <returns>
        ///     <see>
        ///         <cref>System.Threading.Tasks.Task</cref>
        ///     </see>
        ///     类型的对象实例。
        /// </returns>
        /// <seealso>
        ///     <cref>System.Threading.Tasks.Task</cref>
        /// </seealso>
        public async Task DisconnectAsync()
        {
            await InternalDisconnectAsync();
        }

        #endregion

        #endregion
    }
}